package com.leyou.order.service;

import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exception.LyException;
import com.leyou.common.threadlocals.UserHolder;
import com.leyou.common.utils.BeanHelper;
import com.leyou.common.utils.IdWorker;
import com.leyou.item.client.ItemClient;
import com.leyou.item.dto.SkuDTO;
import com.leyou.order.dto.CartDTO;
import com.leyou.order.dto.OrderDTO;
import com.leyou.order.entity.Order;
import com.leyou.order.entity.OrderDetail;
import com.leyou.order.entity.OrderLogistics;
import com.leyou.order.enums.OrderStatusEnum;
import com.leyou.order.mapper.OrderDetailMapper;
import com.leyou.order.mapper.OrderLogisticsMapper;
import com.leyou.order.mapper.OrderMapper;
import com.leyou.order.utils.PayHelper;
import com.leyou.order.vo.OrderDetailVO;
import com.leyou.order.vo.OrderLogisticsVO;
import com.leyou.order.vo.OrderVO;
import com.leyou.user.client.UserClient;
import com.leyou.user.dto.AddressDTO;
import com.netflix.discovery.converters.Auto;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.validation.constraints.NotNull;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private OrderDetailMapper orderDetailMapper; // 在这里就具有批量插入订单详情的功能了

    @Autowired
    private OrderLogisticsMapper orderLogisticsMapper;

    @Autowired
    private IdWorker idWorker;

    @Autowired
    private ItemClient itemClient;

    @Autowired
    private UserClient userClient;

    @Autowired
    private PayHelper payHelper;

    @Autowired
    private StringRedisTemplate redisTemplate;


    @GlobalTransactional // 分布式事务添加
    @Transactional // 需要事务，不能少了本地事务
    public Long createOrder(OrderDTO orderDTO) {
        // 1、插入订单表：tb_order
        Order order = new Order();
        // 1.1生成一个订单id
        long orderId = idWorker.nextId();
        order.setOrderId(orderId);
        order.setUserId(UserHolder.getUser().getId());// 当前登录用户 的id
        order.setStatus(OrderStatusEnum.INIT.value());// 订单状态
        order.setSourceType(2); //订单来源：1:app端，2：pc端，3：微信端
        order.setPaymentType(orderDTO.getPaymentType());//支付类型，1、在线支付，2、货到付款
        order.setPostFee(0L); // 全场包邮
        order.setInvoiceType(0);// 发票类型(0无发票1普通发票，2电子发票，3增值税发票)
        order.setCreateTime(new Date());

        // 总金额计算： 先批量查询sku集合，然后遍历获取价格 * 购物车中的数量 ，最后相加就是总金额
        // 1.2 先获取skuId集合
        List<CartDTO> carts = orderDTO.getCarts();
        // 1.3 把list集合转成Map，把skuId作为key， 购物车num数量作为value
        Map<Long, Integer> cartsMap = carts.stream().collect(Collectors.toMap(CartDTO::getSkuId, CartDTO::getNum));
        // 1.4 获取skuid集合准备批量查询sku
        List<Long> skuIds = carts.stream().map(CartDTO::getSkuId).collect(Collectors.toList());
        // 1.5 批量查询sku
        List<SkuDTO> skuDTOList = itemClient.querySkuListByIds(skuIds);
        // 1.6 使用流的方式计算总金额
        long totalFee = skuDTOList.stream().mapToLong(skuDTO -> skuDTO.getPrice() * cartsMap.get(skuDTO.getId())).sum();
        /*for (SkuDTO skuDTO : skuDTOList) {
                总金额 += skuDTO.getPrice() * cartsMap.get(skuDTO.getId());
        }*/
        // 1.7 设置总金额
        order.setTotalFee( totalFee /* TODO  需要计算出来*/) ; // 总金额
        // 1.8 实付金额
        order.setActualFee(1L); // 实付金额 = 总金额 - 优惠活动的钱 ；现在先写1分钱

        // 插入订单表
        int count = orderMapper.insertSelective(order);
        if(count!=1){
            throw new LyException(ExceptionEnum.INSERT_OPERATION_FAIL);
        }

        // 4、减库存
        itemClient.minusStock(cartsMap);

        // 2、批量插入订单详情条目：条目中需要设置订单的id： tb_order_detail
        List<OrderDetail> orderDetailList = new ArrayList<>();
        // 2.1 遍历sku集合，存入条目集合中
        skuDTOList.forEach(skuDTO -> {
            Long skuIdTemp = skuDTO.getId();
            OrderDetail od = new OrderDetail();
            od.setId(idWorker.nextId());
            od.setOrderId(orderId);
            od.setSkuId(skuIdTemp);
            od.setNum(cartsMap.get(skuIdTemp));
            od.setTitle(skuDTO.getTitle());
            od.setPrice(skuDTO.getPrice());
            od.setOwnSpec(skuDTO.getOwnSpec());
            od.setImage(StringUtils.substringBefore(skuDTO.getImages(),","));// 只获取第一张图片
            od.setCreateTime(new Date());
            orderDetailList.add(od);
        });
        // 2.2 批量插入
        count = orderDetailMapper.insertList(orderDetailList);
        if(count!=orderDetailList.size()){
            throw new LyException(ExceptionEnum.INSERT_OPERATION_FAIL);
        }

        // 模拟异常
        //int i = 1/0;  //模拟异常

        // 3、插入物流表：tb_order_logistics   ： 调用用户微服务【用户微服务有服务间的鉴权】
        AddressDTO addressDTO = userClient.queryAddressById(UserHolder.getUser().getId(), orderDTO.getAddressId());
        OrderLogistics orderLogistics = BeanHelper.copyProperties(addressDTO, OrderLogistics.class);
        // 3.1 设置订单编号
        orderLogistics.setOrderId(orderId);
        // 3.2 插入物流表
        count = orderLogisticsMapper.insertSelective(orderLogistics);
        if(count!=1){
            throw new LyException(ExceptionEnum.INSERT_OPERATION_FAIL);
        }



        // 5、返回订单id
        return orderId;
    }

    public OrderVO queryOrderById(Long id) {
        // 1、查询订单
        Order order = orderMapper.selectByPrimaryKey(id);
        if(order ==null){
            // 不存在
            throw new LyException(ExceptionEnum.ORDER_NOT_FOUND);
        }
        // 细节：判断订单所属人
        Long userId = UserHolder.getUser().getId();
        if(!userId.equals(order.getUserId())){
            throw new LyException(ExceptionEnum.ORDER_NOT_FOUND);
        }

        // 2、查询订单详情
        OrderDetail record = new OrderDetail();
        record.setOrderId(order.getOrderId());
        List<OrderDetail> details = orderDetailMapper.select(record);
        if(CollectionUtils.isEmpty(details)){
            throw new LyException(ExceptionEnum.ORDER_NOT_FOUND);
        }

        // 3、查询订单物流信息
        OrderLogistics orderLogistics = orderLogisticsMapper.selectByPrimaryKey(order.getOrderId());
        if(orderLogistics == null){
            throw new LyException(ExceptionEnum.ORDER_NOT_FOUND);
        }

        // 4、封装OrderVO
        OrderVO orderVO = BeanHelper.copyProperties(order, OrderVO.class);
        orderVO.setLogistics(BeanHelper.copyProperties(orderLogistics, OrderLogisticsVO.class));
        orderVO.setDetailList(BeanHelper.copyWithCollection(details, OrderDetailVO.class));

        // 5、返回OrderVO
        return orderVO;
    }

    // 缓存中的key
    private final static String KEY_PREFIX = "order:url:id:";

    // 获取微信支付的url
    public String queryPayUrl(Long id) {
        // 缓存中key
        String key = KEY_PREFIX + id;
        try {
            // 先从缓存中找，如果缓存中已经有了，那就不需要再去微信端获取
            String cacheUrl = redisTemplate.opsForValue().get(key);
            if(StringUtils.isNotBlank(cacheUrl)){
                return cacheUrl;
            }
        } catch (Exception e) {
            log.error("从缓存中获取微信支付url失败：{}",e.getMessage());
        }
        // ==========================================================

        // 根据订单id查询订单信息
        Order order = orderMapper.selectByPrimaryKey(id);
        // 如果数据库中订单的状态不是初始化，没有必要支付
        if(!OrderStatusEnum.INIT.value().equals(order.getStatus())){
            throw new LyException(ExceptionEnum.INVALID_ORDER_STATUS);
        }
        // 获取总金额
        Long totalFee = order.getActualFee(); // 实付金额，在创建订单的时候我们写死了1分钱

        // 获取连接
        String payUrl = payHelper.getPayUrl(id, totalFee, "【乐优商城】商品购买支付");

        // ==========================================================
        try {
            // 保存到缓存中
            redisTemplate.opsForValue().set(key, payUrl, 2, TimeUnit.HOURS);
        } catch (Exception e) {
            log.error("缓存微信支付url失败：{}",e.getMessage());
        }
        // ==========================================================
        // 返回支付的url
        return payUrl;
    }

    // 微信异步通知
    public Map<String, String> wxNotify(Map<String, String> data) {

        // 接收到微信的通知，如果是成功，我们要修改订单的状态
        // 检验业务标识
        payHelper.checkResultCode(data);
        // 检验签名
        payHelper.checkSignature(data);
        // 获取支付信息
        String out_trade_no = data.get("out_trade_no");
        // 获取总金额
        String total_fee = data.get("total_fee");
        if(StringUtils.isBlank(out_trade_no) || StringUtils.isBlank(total_fee)){
            throw new LyException(ExceptionEnum.INVALID_PARAM_ERROR);
        }
        // 把字符串转成数字
        Long orderId = Long.valueOf(out_trade_no);
        Long totalFee = Long.valueOf(total_fee);
        // 查询订单
        Order order = orderMapper.selectByPrimaryKey(orderId);
        // 比对实付金额实付匹配
        if(!totalFee.equals(order.getActualFee())){
            throw new LyException(ExceptionEnum.INVALID_PARAM_ERROR);
        }
        // 幂等校验
        if(OrderStatusEnum.INIT.value().equals(order.getStatus())){
            // 更新订单的状态
            Order record = new Order();
            record.setOrderId(orderId);
            record.setStatus(OrderStatusEnum.PAY_UP.value());// 修改状态
            record.setPayTime(new Date());
            int count = orderMapper.updateByPrimaryKeySelective(record);
            if(count!=1){
                throw new LyException(ExceptionEnum.INVALID_PARAM_ERROR);
            }
        }
        log.info("接收微信支付通知成功！");
        // 返回给微信的信息，如果没有返回，微信会一直通知我们15次
        Map<String, String> result = new HashMap<>();
        result.put("return_code", "SUCCESS");
        result.put("return_msg", "OK");
        return result;
    }

    public Integer checkOrderState(Long id) {
        // 检查订单状态
        Order order = orderMapper.selectByPrimaryKey(id);
        if(order == null){
            throw new LyException(ExceptionEnum.INVALID_ORDER_STATUS);
        }
        return order.getStatus();
    }
}
